<!DOCTYPE html>
<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  
  <meta name="author" content="Mouse0w0">
  
  <link rel="shortcut icon" href="../img/favicon.ico">
  <title>地形碰撞 - Lwjglbook中文翻译</title>
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lato:400,700|Roboto+Slab:400,700|Inconsolata:400,700" />

  <link rel="stylesheet" href="../css/theme.css" />
  <link rel="stylesheet" href="../css/theme_extra.css" />
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/github.min.css" />
  
  <script>
    // Current page data
    var mkdocs_page_name = "\u5730\u5f62\u78b0\u649e";
    var mkdocs_page_input_path = "15-terrain-collisions.md";
    var mkdocs_page_url = null;
  </script>
  
  <script src="../js/jquery-2.1.1.min.js" defer></script>
  <script src="../js/modernizr-2.8.3.min.js" defer></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
  <script>hljs.initHighlightingOnLoad();</script> 
  
</head>

<body class="wy-body-for-nav" role="document">

  <div class="wy-grid-for-nav">

    
    <nav data-toggle="wy-nav-shift" class="wy-nav-side stickynav">
    <div class="wy-side-scroll">
      <div class="wy-side-nav-search">
        <a href=".." class="icon icon-home"> Lwjglbook中文翻译</a>
        <div role="search">
  <form id ="rtd-search-form" class="wy-form" action="../search.html" method="get">
    <input type="text" name="q" placeholder="Search docs" title="Type search term here" />
  </form>
</div>
      </div>

      <div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../01-first-steps/">事前准备</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../02-the-game-loop/">游戏循环</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../03-a-brief-about-coordinates/">坐标简介</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../04-rendering/">渲染</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../05-more-on-rendering/">渲染补充</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../06-transformations/">变换</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../07-textures/">纹理</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../08-camera/">摄像机</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../09-loading-more-complex-models/">加载更复杂的模型</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../10-let-there-be-light/">要有光</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../11-let-there-be-even-more-light/">要有更多的光</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../12-game-hud/">游戏HUD</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../13-sky-box-and-some-optimizations/">天空盒与一些优化</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../14-height-maps/">高度图</a>
                    </li>
                </ul>
                <ul class="current">
                    <li class="toctree-l1 current"><a class="reference internal current" href="./">地形碰撞</a>
    <ul class="current">
    </ul>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../16-fog/">雾</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../17-normal-mapping/">法线贴图</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../18-shadows/">阴影</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../19-animations/">动画</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../20-particles/">粒子</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../21-instanced-rendering/">实例化渲染</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../22-audio/">音效</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../23-3d-object-picking/">三维物体选取</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../24-hud-revisited/">回顾HUD - NanoVG</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../25-optimizations-frustum-culling/">优化 - 截锥剔除</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../26-cascaded-shadow-maps/">级联阴影映射</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../27-assimp/">Assimp库</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../28-deferred-shading/">延迟着色法</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../a01-opengl-debugging/">附录 A - OpenGL调试</a>
                    </li>
                </ul>
                <ul>
                    <li class="toctree-l1"><a class="reference internal" href="../glossary/">术语表</a>
                    </li>
                </ul>
      </div>
    </div>
    </nav>

    <section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">

      
      <nav class="wy-nav-top" role="navigation" aria-label="top navigation">
        <i data-toggle="wy-nav-top" class="fa fa-bars"></i>
        <a href="..">Lwjglbook中文翻译</a>
      </nav>

      
      <div class="wy-nav-content">
        <div class="rst-content">
          <div role="navigation" aria-label="breadcrumbs navigation">
  <ul class="wy-breadcrumbs">
    <li><a href="..">Docs</a> &raquo;</li>
    
      
    
    <li>地形碰撞</li>
    <li class="wy-breadcrumbs-aside">
      
        <a href="https://github.com/Mouse0w0/lwjglbook-CN-Translation/edit/master/docs/15-terrain-collisions.md"
          class="icon icon-github"> Edit on GitHub</a>
      
    </li>
  </ul>
  
  <hr/>
</div>
          <div role="main">
            <div class="section">
              
                <h1 id="terrain-collisions">地形碰撞（Terrain Collisions）</h1>
<p>此前我们创建了一个地形，接下来就是检测碰撞以避免穿过它。回忆一下之前的内容，一个地形是由地形块组成的，每个地形块都是由高度图生成的，高度图用于设置构成地形的三角形的顶点高度。</p>
<p>为了检测碰撞，我们必须将当前所在位置的<strong>Y</strong>值与当前地形点的<strong>Y</strong>值进行比较。如果有碰撞，我们需要回到地形上方。很简单的想法，是吗？确实是这样，但在比较之前，我们需要进行几次计算。</p>
<p>我们首先要定义的是我们对“当前位置”这个词的理解。由于我们还没有一个球员的概念，答案很简单，当前的位置将是相机的位置。因此，我们已经有了比较的组成部分之一，因此，接下来要计算的是当前位置的地形高度。
首先要定义的是“当前位置”这个词的概念。由于我们还没有一个“玩家”的概念，因此当前位置将是摄像机的位置。这样我们就有了比较的一方，因此接下来要计算当前位置的地形高度。</p>
<p>如上所是，地形由地形块组成，如下图所示。</p>
<p><img alt="地形网格" src="../_static/15/terrain_grid.png" /></p>
<p>每个地形块都是由相同的高度图网格构成，但被精确地缩放和位移，以形成看起来像是连续的景观的地形网格。</p>
<p>所以首先要做的是确定当前位置(摄像机位置)在哪个地形块。为了得到它，我们将基于位移和缩放来计算每个地形块的包围盒(<strong>BoundingBox</strong>)。因为地形在运行时不会移动或缩放，所以我们可以在<code>Terrain</code>类的构造方法中计算。这样就可以在任何时候访问它们，而不需要在每个游戏循环周期中重复这些计算。</p>
<p>我们将创建一个新的方法来计算一个地形块的包围盒，名为<code>getBoundingBox</code>。</p>
<pre><code class="java">private Box2D getBoundingBox(GameItem terrainBlock) {
    float scale = terrainBlock.getScale();
    Vector3f position = terrainBlock.getPosition();

    float topLeftX = HeightMapMesh.STARTX * scale + position.x;
    float topLeftZ = HeightMapMesh.STARTZ * scale + position.z;
    float width = Math.abs(HeightMapMesh.STARTX * 2) * scale;
    float height = Math.abs(HeightMapMesh.STARTZ * 2) * scale;
    Box2D boundingBox = new Box2D(topLeftX, topLeftZ, width, height);
    return boundingBox;
}
</code></pre>

<p><code>Box2D</code>是<code>java.awt.Rectangle2D.Float</code>类的简化版本，为了避免使用AWT而创建。</p>
<p>限制我们需要计算地形块的世界坐标。在上一章中，你看到所有的地形网格都是在一个正方形中创建的，它的原点设置为<code>[STARTX, STARTZ]</code>。因此，我们需要把这些坐标转换为世界坐标，这要考虑下图所示的位移与缩放。</p>
<p><img alt="模型坐标系到世界坐标系" src="../_static/15/model_to_world_coordinates.png" /></p>
<p>如上所述，这可以在<code>Terrain</code>类构造方法中计算，因为它不会在运行时发生变化，所以我们要添加一个新的属性来保存包围盒：</p>
<pre><code class="java">private final Box2D[][] boundingBoxes;
</code></pre>

<p>在<code>Terrain</code>类的构造方法中，当我们创建地形块时，只需调用计算包围盒的方法。</p>
<pre><code class="java">public Terrain(int terrainSize, float scale, float minY, float maxY, String heightMapFile, String textureFile, int textInc) throws Exception {
    this.terrainSize = terrainSize;
    gameItems = new GameItem[terrainSize * terrainSize];

    PNGDecoder decoder = new PNGDecoder(getClass().getResourceAsStream(heightMapFile));
    int height = decoder.getHeight();
    int width = decoder.getWidth();
    ByteBuffer buf = ByteBuffer.allocateDirect(
            4 * decoder.getWidth() * decoder.getHeight());
    decoder.decode(buf, decoder.getWidth() * 4, PNGDecoder.Format.RGBA);
    buf.flip();

    // 每行与每列的顶点数
    verticesPerCol = heightMapImage.getWidth();
    verticesPerRow = heightMapImage.getHeight();

    heightMapMesh = new HeightMapMesh(minY, maxY, buf, width, textureFile, textInc);
    boundingBoxes = new Box2D[terrainSize][terrainSize];
    for (int row = 0; row &lt; terrainSize; row++) {
        for (int col = 0; col &lt; terrainSize; col++) {
            float xDisplacement = (col - ((float) terrainSize - 1) / (float) 2) * scale * HeightMapMesh.getXLength();
            float zDisplacement = (row - ((float) terrainSize - 1) / (float) 2) * scale * HeightMapMesh.getZLength();

            GameItem terrainBlock = new GameItem(heightMapMesh.getMesh());
            terrainBlock.setScale(scale);
            terrainBlock.setPosition(xDisplacement, 0, zDisplacement);
            gameItems[row * terrainSize + col] = terrainBlock;

            boundingBoxes[row][col] = getBoundingBox(terrainBlock);
        }
    }
}
</code></pre>

<p>因此，有了所有预先计算的包围盒，我们将创建一个新的方法，这个方法将以当前位置为参数，返回对应地形高度。该方法名为<code>getHeight</code>，其定义如下。</p>
<pre><code class="java">public float getHeight(Vector3f position) {
    float result = Float.MIN_VALUE;
    // 对于每个地形块，我们获取包围盒，将其转换到观察坐标系
    // 检查坐标是否包含在包围盒中
    Box2D boundingBox = null;
    boolean found = false;
    GameItem terrainBlock = null;
    for (int row = 0; row &lt; terrainSize &amp;&amp; !found; row++) {
        for (int col = 0; col &lt; terrainSize &amp;&amp; !found; col++) {
            terrainBlock = gameItems[row * terrainSize + col];
            boundingBox = boundingBoxes[row][col];
            found = boundingBox.contains(position.x, position.z);
        }
    }

    // 如果我们找到了一个包含我们位置的地形块
    // 计算该位置的地形高度
    if (found) {
        Vector3f[] triangle = getTriangle(position, boundingBox, terrainBlock);
        result = interpolateHeight(triangle[0], triangle[1], triangle[2], position.x, position.z);
    }

    return result;
}
</code></pre>

<p>在此方法中第一件事是确定我们所在的地形块。由于我们已经有了每个地形块的包围盒，所以算法很简单。我们只需要迭代包围盒数组，并检查当前位置是否位于其中(<code>Box2D</code>提供了该方法)。</p>
<p>一旦找到了地形块，我们需要计算所处的三角形，这是由之后的<code>getTriangle</code>方法计算的。之后，我们得到了所在三角形的坐标，包括它的高度。但是，我们需要的是一个点的高度，这个点不位于这些顶点中的任何一点，而位于它们之间的位置。这将在<code>interpolateHeight</code>方法中计算，我们也将解释这是如何计算的。</p>
<p>让我们先从确定所处的三角形开始。构成地形块的正方形可以看作一个网格，其中每个单元由两个三角形组成。首先我们定义一些变量：</p>
<ul>
<li><strong>boundingBox.x</strong>是与包围盒相关联的地形块的原<strong>x</strong>坐标。</li>
<li><strong>boundingBox.y</strong>是与包围盒相关联的地形块的原<strong>z</strong>坐标(即使你看到一个<strong>y</strong>，但它是在<strong>z</strong>轴的)。</li>
<li><strong>boundingBox.width</strong>是地形块正方形的宽度。</li>
<li><strong>boundingBox.height</strong>是地形块正方形的高度。</li>
<li><strong>cellWidth</strong>是一个单元的宽度。</li>
<li><strong>cellHeight</strong>是一个单元的高度。</li>
</ul>
<p>上面定义的所有变量都用世界坐标来表示。为了计算单元的宽度，我们只需要将包围盒宽度除以每列的顶点数：</p>
<p>
<script type="math/tex; mode=display">cellWidth = \frac{boundingBox.width}{verticesPerCol}</script>
</p>
<p><code>cellHeight</code>的计算也相似：</p>
<p>
<script type="math/tex; mode=display">cellHeight = \frac{boundingBox.height}{verticesPerRow}</script>
</p>
<p>一旦有了这些变量，我们就可以计算所在的单元格的行和列了：</p>
<p>
<script type="math/tex; mode=display">col = \frac{position.x - boundingBox.x}{boundingBox.width}</script>
</p>
<p>
<script type="math/tex; mode=display">row = \frac{position.z - boundingBox.y}{boundingBox.height}</script>
</p>
<p>下图在示例地形块展示了此前描述的所有变量。</p>
<p><img alt="地形块变量" src="../_static/15/terrain_block_variables_n.png" /></p>
<p>有了这些信息，就可以计算单元格中包含的三角形顶点的位置。我们怎么才能做到呢？让我们来看看组成一个单元格的三角形。</p>
<p><img alt="单元格" src="../_static/15/cell.png" /></p>
<p>你可以看到，单元格是被一个对角线分开为两个三角形的。确定与当前位置相关的三角形的方法，是检查<strong>z</strong>坐标在对角线的上方还是下方。在本例中，将对角线的<strong>x</strong>值设置为当前位置的<strong>x</strong>值，求出对应的对角线<strong>z</strong>值，如果当前位置的<strong>z</strong>值小于对角线的<strong>z</strong>值，那么我们在<strong>T1</strong>中。反之如果当前位置的<strong>z</strong>值大于对角线的<strong>z</strong>值，我们就在<strong>T2</strong>中。</p>
<p>我们可以通过计算与对角线相匹配的直线方程来确定。</p>
<p>如果你还记得学校的数学课，从两点通过的直线(在二维中)的方程为:</p>
<p>
<script type="math/tex; mode=display">y-y1=m\cdot(x-x1)</script>
</p>
<p>其中m是直线的斜率，也就是说，当沿<strong>x</strong>轴移动时，其高度会发生变化。请注意，在本例中，<strong>y</strong>坐标其实是一个<strong>z</strong>。还要注意的是，我们使用的是二维坐标，因为在这里不计算高度，只要<strong>x</strong>坐标和<strong>z</strong>坐标就足够了。因此，在本例中，直线方程应该是这样。</p>
<p>
<script type="math/tex; mode=display">z-z1=m\cdot(x-x1)</script>
</p>
<p>斜率可以按如下方式计算：</p>
<p>
<script type="math/tex; mode=display">m=\frac{z1-z2}{x1-x2}</script>
</p>
<p>所以给定一个<strong>x</strong>坐标得到一个<strong>z</strong>值的对角线方程就像这样：</p>
<p>
<script type="math/tex; mode=display">z=m\cdot(xpos-x1)+z1=\frac{z1-z2}{x1-x2}\cdot(zpos-x1)+z1</script>
</p>
<p>其中<strong>x1</strong>、<strong>x2</strong>、<strong>z1</strong>和<strong>z2</strong>分别是顶点<strong>V1</strong>和<strong>V2</strong>的<strong>x</strong>和<strong>z</strong>坐标。</p>
<p>因此，通过上述方式来获得当前位置所在的三角形的方法，名为<code>getTriangle</code>，其实现如下：</p>
<pre><code class="java">protected Vector3f[] getTriangle(Vector3f position, Box2D boundingBox, GameItem terrainBlock) {
    // 获得与当前位置相关的高度图的行列
    float cellWidth = boundingBox.width / (float) verticesPerCol;
    float cellHeight = boundingBox.height / (float) verticesPerRow;
    int col = (int) ((position.x - boundingBox.x) / cellWidth);
    int row = (int) ((position.z - boundingBox.y) / cellHeight);

    Vector3f[] triangle = new Vector3f[3];
    triangle[1] = new Vector3f(
        boundingBox.x + col * cellWidth,
        getWorldHeight(row + 1, col, terrainBlock),
        boundingBox.y + (row + 1) * cellHeight);
    triangle[2] = new Vector3f(
        boundingBox.x + (col + 1) * cellWidth,
        getWorldHeight(row, col + 1, terrainBlock),
        boundingBox.y + row * cellHeight);
    if (position.z &lt; getDiagonalZCoord(triangle[1].x, triangle[1].z, triangle[2].x, triangle[2].z, position.x)) {
        triangle[0] = new Vector3f(
            boundingBox.x + col * cellWidth,
            getWorldHeight(row, col, terrainBlock),
            boundingBox.y + row * cellHeight);
    } else {
        triangle[0] = new Vector3f(
            boundingBox.x + (col + 1) * cellWidth,
            getWorldHeight(row + 2, col + 1, terrainBlock),
            boundingBox.y + (row + 1) * cellHeight);
    }

    return triangle;
}

protected float getDiagonalZCoord(float x1, float z1, float x2, float z2, float x) {
    float z = ((z1 - z2) / (x1 - x2)) * (x - x1) + z1;
    return z;
}

protected float getWorldHeight(int row, int col, GameItem gameItem) {
    float y = heightMapMesh.getHeight(row, col);
    return y * gameItem.getScale() + gameItem.getPosition().y;
}
</code></pre>

<p>你可以看到我们有另外两个反复。第一个名为<code>getDiagonalZCoord</code>，给定<strong>x</strong>位置和两个顶点计算对角线的<strong>z</strong>坐标。另一个名为<code>getWorldHeight</code>，用来获得三角形顶点的高度(即<strong>y</strong>坐标)。当地形网格被创建时，每个顶点的高度都被预先计算和储存，我们只需将其转换为世界坐标。</p>
<p>好，我们有当前位置的三角形坐标。最后，我们准备在当前位置计算地形高度。怎么做呢？我们的三角形在一个平面上，一个平面可以由三个点定义，在本例中，三个顶点定义了一个三角形。</p>
<p>平面方程如下：</p>
<p>
<script type="math/tex; mode=display">a\cdot x+b\cdot y+c\cdot z+d=0</script>
</p>
<p>上述方程的常数值是：</p>
<p>
<script type="math/tex; mode=display">a=(B_{y}-A_{y}) \cdot (C_{z} - A_{z}) - (C_{y} - A_{y}) \cdot (B_{z}-A_{z})</script>
</p>
<p>
<script type="math/tex; mode=display">b=(B_{z}-A_{z}) \cdot (C_{x} - A_{x}) - (C_{z} - A_{z}) \cdot (B_{z}-A_{z})</script>
</p>
<p>
<script type="math/tex; mode=display">c=(B_{x}-A_{x}) \cdot (C_{y} - A_{y}) - (C_{x} - A_{x}) \cdot (B_{y}-A_{y})</script>
</p>
<p>其中<strong>A</strong>、<strong>B</strong>和<strong>C</strong>是定义平面所需的三个顶点。</p>
<p>然后，利用之前的方程以及当前位置的<strong>x</strong>和<strong>z</strong>坐标值，我们能够计算<strong>y</strong>值，即当前位置的地形高度：</p>
<p>
<script type="math/tex; mode=display">y = (-d - a \cdot x - c \cdot z) / b</script>
</p>
<p>实现了如上运算的方法如下：</p>
<pre><code class="java">protected float interpolateHeight(Vector3f pA, Vector3f pB, Vector3f pC, float x, float z) {
    // 平面方程 ax+by+cz+d=0
    float a = (pB.y - pA.y) * (pC.z - pA.z) - (pC.y - pA.y) * (pB.z - pA.z);
    float b = (pB.z - pA.z) * (pC.x - pA.x) - (pC.z - pA.z) * (pB.x - pA.x);
    float c = (pB.x - pA.x) * (pC.y - pA.y) - (pC.x - pA.x) * (pB.y - pA.y);
    float d = -(a * pA.x + b * pA.y + c * pA.z);
    // y = (-d -ax -cz) / b
    float y = (-d - a * x - c * z) / b;
    return y;
}
</code></pre>

<p>这就完了！现在我们能够检测碰撞，所以在<code>DummyGame</code>类中，在更新摄像机位置时，修改如下代码：</p>
<pre><code class="java">// 更新摄像机位置
Vector3f prevPos = new Vector3f(camera.getPosition());
camera.movePosition(cameraInc.x * CAMERA_POS_STEP, cameraInc.y * CAMERA_POS_STEP, cameraInc.z * CAMERA_POS_STEP);        
// 检查是否发生碰撞。如果为true，将y坐标设置为
// 最大高度
float height = terrain.getHeight(camera.getPosition());
if ( camera.getPosition().y &lt;= height )  {
    camera.setPosition(prevPos.x, prevPos.y, prevPos.z);
}
</code></pre>

<p>如你所见，检测地形碰撞的概念很容易理解，但是我们需要仔细地进行计算并了解正处理的不同坐标系。</p>
<p>此外，虽然这里给出的算法在大多数情况下都是可用的，但仍存在需要仔细处理的情况。你可以发现的一个问题是隧道效应(<code>Tunnelling</code>)。设想一个情况，我们正以高速穿过地形，正因如此，位置增量值较高。这个值变得如此之高，以至于因为我们检测的是最终位置的碰撞，所以可能已经穿过了位于两点之间的障碍。</p>
<p><img alt="隧道效应" src="../_static/15/tunnelling.png" /></p>
<p>有许多可行的解决方案可以避免这个效应，最简单的解决方法是将要进行的计算分成增量较小的多份。</p>
              
            </div>
          </div>
          <footer>
  
    <div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
      
        <a href="../16-fog/" class="btn btn-neutral float-right" title="雾">Next <span class="icon icon-circle-arrow-right"></span></a>
      
      
        <a href="../14-height-maps/" class="btn btn-neutral" title="高度图"><span class="icon icon-circle-arrow-left"></span> Previous</a>
      
    </div>
  

  <hr/>

  <div role="contentinfo">
    <!-- Copyright etc -->
    
      <p>2019, Mouse0w0</p>
    
  </div>

  Built with <a href="https://www.mkdocs.org/">MkDocs</a> using a <a href="https://github.com/snide/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
      
        </div>
      </div>

    </section>

  </div>

  <div class="rst-versions" role="note" aria-label="versions">
    <span class="rst-current-version" data-toggle="rst-current-version">
      
          <a href="https://github.com/Mouse0w0/lwjglbook-CN-Translation/" class="fa fa-github" style="float: left; color: #fcfcfc"> GitHub</a>
      
      
        <span><a href="../14-height-maps/" style="color: #fcfcfc;">&laquo; Previous</a></span>
      
      
        <span style="margin-left: 15px"><a href="../16-fog/" style="color: #fcfcfc">Next &raquo;</a></span>
      
    </span>
</div>
    <script>var base_url = '..';</script>
    <script src="../js/theme.js" defer></script>
      <script src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML" defer></script>
      <script src="../search/main.js" defer></script>
    <script defer>
        window.onload = function () {
            SphinxRtdTheme.Navigation.enable(true);
        };
    </script>

</body>
</html>
